package com.exam.api.common.cardRecognition;

import cn.hutool.core.util.StrUtil;
import lombok.extern.log4j.Log4j2;
import org.opencv.core.*;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@Component
@Log4j2
public class OpenCVUtil {

    // 根据三个点计算中间那个点的夹角   pt1 pt0 pt2
    public static double getAngle(Point pt1, Point pt2, Point pt0){
        double dx1 = pt1.x - pt0.x;
        double dy1 = pt1.y - pt0.y;
        double dx2 = pt2.x - pt0.x;
        double dy2 = pt2.y - pt0.y;
        return (dx1*dx2 + dy1*dy2)/Math.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
    }

    // 找到最大的正方形轮廓
    public static int findLargestSquare(List<MatOfPoint> squares) {
        if (squares.size() == 0)
            return -1;
        int max_width = 0;
        int max_height = 0;
        int max_square_idx = 0;
        int currentIndex = 0;
        for (MatOfPoint square : squares) {
            Rect rectangle = Imgproc.boundingRect(square);
            if (rectangle.width >= max_width && rectangle.height >= max_height) {
                max_width = rectangle.width;
                max_height = rectangle.height;
                max_square_idx = currentIndex;
            }
            currentIndex++;
        }
        return max_square_idx;
    }

    // 点到点的距离
    public static double getSpacePointToPoint(Point p1, Point p2) {
        double a = p1.x - p2.x;
        double b = p1.y - p2.y;
        return Math.sqrt(a * a + b * b);
    }

    // 两直线的交点
    public static Point computeIntersect(double[] a, double[] b) {
        if (a.length != 4 || b.length != 4)
            throw new ClassFormatError();
        double x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3], x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
        double d = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4));
        if (d != 0) {
            Point pt = new Point();
            pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
            pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
            return pt;
        }
        else
            return new Point(-1, -1);
    }

    // 对多个点按顺时针排序
    public static void sortCorners(List<Point> corners) {
        if (corners.size() == 0) return;
        Point p1 = corners.get(0);
        int index = 0;
        for (int i = 1; i < corners.size(); i++) {
            Point point = corners.get(i);
            if (p1.x > point.x) {
                p1 = point;
                index = i;
            }
        }

        corners.set(index, corners.get(0));
        corners.set(0, p1);

        Point lp = corners.get(0);
        for (int i = 1; i < corners.size(); i++) {
            for (int j = i + 1; j < corners.size(); j++) {
                Point point1 = corners.get(i);
                Point point2 = corners.get(j);
                if ((point1.y-lp.y*1.0)/(point1.x-lp.x)>(point2.y-lp.y*1.0)/(point2.x-lp.x)) {
                    Point temp = point1.clone();
                    corners.set(i, corners.get(j));
                    corners.set(j, temp);
                }
            }
        }
    }



    /**
     * 根据列单独处理
     * @Author Songer
     * @param cut1 传入的答案列，一般1-20一列
     * @param temp 表格范围mat
     * @Date 2018年9月20日
     * 更新日志
     * 2018年9月20日 Songer  首次创建
     *
     */
    private List<String> processByCol(Mat cut1,Mat cut_gray,Mat temp,int answerCols,String outputDir) {
        List<String> result = new ArrayList<String>();
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        List<MatOfPoint> answerList = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();
        //进行轮廓查找，注意因为该答题卡特征是闭合填图区域，所以用这种方式，如果是非闭合填涂区域，则不适用
        Imgproc.findContours(cut1.clone(), contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//		 log.info(contours.size());
//		 log.info("-----w------"+(temp.width()*80/2693-20));
//		 log.info("-----h------"+(temp.height()*80/2764-20));
        for(int i = 0;i<contours.size();i++){
            MatOfPoint mop= contours.get(i);
            //每个轮廓给出外包矩形，便于操作
            Rect rect = Imgproc.boundingRect(mop);
//     		log.info("-----------"+rect.width+"	"+rect.height);
            //一个填图区域大概占整个表格的w:80/2733 h:0/2804,，所以排除掉太小的轮廓和过大的轮廓
//     		//绘制每个轮廓图
//     		Imgproc.drawContours(cut1, contours, i, new Scalar(170), 2);
            //此处是为了排除杂点，较小或较大的轮廓都是非填图选项，可以排除，可以按实际情况灵活变动限制条件，最好输出出轮廓便于观察
//            if(rect.width>(temp.width()*80/2693-20) && rect.height>(temp.height()*80/2764-20) && rect.width<temp.width()*0.05 && rect.height<temp.height()*0.05){
            if(rect.width> 30 && rect.height> 20){
//     		if(rect.width>50&&rect.height>50&&rect.area()>2500&&rect.area()<10000){
//     			Core.rectangle(img_cut, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y
//						+ rect.height), new Scalar(0, 255, 0), 2);
                answerList.add(mop);

            }
        }

        Collections.sort(answerList, new Comparator<MatOfPoint>() {
            //按照y坐标升序排列
            @Override
            public int compare(MatOfPoint o1, MatOfPoint o2) {
                Rect rect1 = Imgproc.boundingRect(o1);
                Rect rect2 = Imgproc.boundingRect(o2);
                if(rect1.y<rect2.y){
                    return -1;
                }else if(rect1.y>rect2.y){
                    return 1;
                }else{
                    return 0;
                }
            }
        });

        String resultChoose = "";
        for (int j = 0; j < answerList.size(); j++) {
            Imgproc.drawContours(cut_gray, answerList, j, new Scalar(170), 2);
            //掩模提取出轮廓
            Mat mask = Mat.zeros(cut_gray.size(), CvType.CV_8UC1);
            //绘制轮廓便于观察
            Imgproc.drawContours(mask, answerList, j, new Scalar(255), -1);
            Mat dst = new Mat();
            Core.bitwise_and(cut_gray, mask, dst);
            //获取填涂百分比,填涂区域的二值化后取出非0点/掩模的轮廓面积
            double p100 = Core.countNonZero(dst) * 100 / Core.countNonZero(mask);
            String anno = index2ColName(j);
            //认为非0像素超过80%的算是填涂
            if(StrUtil.isNotEmpty(outputDir)){
                String destPath = outputDir + "cardResult_5.png";
                Highgui.imwrite(destPath, dst);
            }

            if(p100>80){
                resultChoose += anno;
                log.info(p100+" 第行:选项（"+anno+"）填涂");
            }else{
                log.info(p100+" 第行:选项（"+anno+"）未填涂");
            }
        }
        result.add(resultChoose);

        //每4/5个一组组成新list，并按x坐标排序
        //该方式依赖于预处理后的图片没有干扰轮廓。如果图像处理不佳，干扰项没排除，那么此处的分组就会错乱。
        //提供一个优化思路：对于闭合填涂区域的可以使用水平垂直投影进行坐标定位点的判定。
        int queno = 1;
        int totoSize = answerList.size();
        for (int i = 0; i < totoSize; i+=answerCols) {
            int toIndex = i+answerCols;
            if(toIndex>totoSize){
                toIndex = totoSize-1;
            }
            List<MatOfPoint> newList = answerList.subList(i,toIndex);

            for (int j = 0; j < newList.size(); j++) {
                Imgproc.drawContours(cut_gray, newList, j, new Scalar(170), 2);
                //掩模提取出轮廓
                Mat mask = Mat.zeros(cut_gray.size(), CvType.CV_8UC1);
                //绘制轮廓便于观察
                Imgproc.drawContours(mask, newList, j, new Scalar(255), -1);
                Mat dst = new Mat();
                Core.bitwise_and(cut_gray, mask, dst);
                //获取填涂百分比,填涂区域的二值化后取出非0点/掩模的轮廓面积
                double p100 = Core.countNonZero(dst) * 100 / Core.countNonZero(mask);
                //认为非0像素超过80%的算是填涂
                if(p100>80){
                    log.info(p100+" 第"+queno+"行:选项填涂");
                }else{
                    log.info(p100+" 第"+queno+"行:选项填涂");
                }
            }
            result.add(resultChoose);
            queno++;
        }

        hierarchy.release();
        for (MatOfPoint contour : contours) {
            contour.release();
        }
//        if(StrUtil.isNotEmpty(outputDir)){
//            String destPath = outputDir + "cardResult_5.png";
//            Highgui.imwrite(destPath, cut1);
//        }
        return result;
    }

    //编号转答案0-A 1-B
    public String index2ColName(int index){
        if (index < 0) {
            return null;
        }
        int num = 65;// A的Unicode码
        String colName = "";
        do {
            if (colName.length() > 0) {
                index--;
            }
            int remainder = index % 26;
            colName = ((char) (remainder + num)) + colName;
            index = (int) ((index - remainder) / 26);
        } while (index > 0);
        return colName;
    }

}
